use glam::{dvec3, DVec3};

use crate::{material::MatIsotropic, HitRecord, Hitable, Interval, MaterialRef, Ray, AABB, random_standard_uniform};

pub struct ConstantMedium {
    pub boundary: Box<dyn Hitable>,
    pub neg_inv_density: f64,
    pub phase_function: MaterialRef,
}

impl ConstantMedium {
    pub fn new(boundary: Box<dyn Hitable>, density: f64, color: DVec3) -> Self {
        Self {
            boundary,
            neg_inv_density: -1.0 / density,
            phase_function: MatIsotropic::new(color),
        }
    }
}

impl Hitable for ConstantMedium {
    fn hit(&self, ray: &Ray, interval: Interval) -> Option<HitRecord> {
        let hit1 = self.boundary.hit(ray, Interval::new(std::f64::NEG_INFINITY, std::f64::INFINITY));
        if hit1.is_none() { return None }

        let mut hit1 = hit1.unwrap();

        let hit2 = self.boundary.hit(ray, Interval::new(hit1.t + 0.0001,  std::f64::INFINITY));
        if hit2.is_none() { return None }

        let mut hit2 = hit2.unwrap();

        hit1.t = hit1.t.max(interval.min);
        hit2.t = hit2.t.min(interval.max);

        if hit1.t >= hit2.t { return None }

        if hit1.t < 0.0 { 
            hit1.t = 0.0;
        }

        let ray_length = ray.dir.length();
        let distance_inside_boundary = (hit2.t - hit1.t) * ray_length;
        let hit_distance = self.neg_inv_density * random_standard_uniform().ln();

        if hit_distance > distance_inside_boundary { return None }

        let t = hit1.t + hit_distance / ray_length;
        let p = ray.at(t);

        let mut hit = HitRecord::new(t, p, self.phase_function.clone());
        hit.front_face = true;
        hit.n = dvec3(1.0, 0.0, 0.0);

        Some(hit)
    }

    fn bounding_box(&self) -> &AABB {
        self.boundary.bounding_box()
    }
}
